1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.systems.gamepad; 12 13 import hip.event.handlers.button; 14 public import hip.api.input.gamepad; 15 public import hip.math.vector; 16 import hip.systems.gamepads.xbox; 17 import hip.systems.gamepads.psv; 18 19 20 enum HipGamepadTypes : ubyte 21 { 22 xbox, 23 psvita 24 } 25 26 HipGamepad getNewGamepad(ubyte type) 27 { 28 final switch(type) 29 { 30 case HipGamepadTypes.xbox: return new HipGamepad(new HipXBOXGamepad()); 31 case HipGamepadTypes.psvita: return new HipGamepad(new HipPSVGamepad()); 32 } 33 } 34 35 interface IHipGamepadImpl 36 { 37 void poll(HipGamepad pad); 38 void setVibrating(ubyte id, 39 double leftMotor, 40 double rightMotor, 41 double leftTrigger, 42 double rightTrigger 43 ); 44 bool isWireless(ubyte id); 45 HipGamepadBatteryStatus getBatteryStatus(ubyte id); 46 } 47 final class HipNullGamepad : IHipGamepadImpl 48 { 49 void poll(HipGamepad pad){} 50 void setVibrating(ubyte id, double leftMotor, double rightMotor, double leftTrigger, double rightTrigger){} 51 bool isWireless(ubyte id){return false;} 52 HipGamepadBatteryStatus getBatteryStatus(ubyte id){return HipGamepadBatteryStatus.init;} 53 } 54 55 56 private pragma(inline, true) float applyDeadzone(float input, float deadzone) 57 { 58 if(input < 0) return input > -deadzone ? 0 : input; 59 return input < deadzone ? 0 : input; 60 } 61 62 private pragma(inline, true) float applyZones(float input, float deadzone, float alivezone) 63 { 64 input = applyDeadzone(input, deadzone); 65 if(input < 0) return input < -alivezone ? -1 : input; 66 return input > alivezone ? 1 : input; 67 } 68 69 70 /** Engine task: 71 * Send gamepad connect and disconnect events 72 * Poll every connected gamepad every frame 73 * Should always return a neutral state for every disconnected gamepad 74 * 75 * Game task: 76 * Check gamepad count 77 * Decide what will do with connected gamepads 78 */ 79 class HipGamepad : AHipGamepad 80 { 81 HipGamepadBatteryStatus status; 82 Vector3 leftAnalog; 83 Vector3 rightAnalog; 84 float leftTrigger; 85 float rightTrigger; 86 ubyte id; 87 __gshared ubyte instanceCount = 0; 88 protected float vibrationAccumulator = 0; 89 protected HipButtonMetadata[HipGamepadButton.count] buttons; 90 private IHipGamepadImpl impl; 91 92 93 94 ubyte getId(){return id;} 95 package this(IHipGamepadImpl impl) 96 { 97 id = instanceCount++; 98 for(int i = 0; i < buttons.length; i++) 99 buttons[i] = new HipButtonMetadata(i); 100 this.impl = impl; 101 } 102 103 void setButtonPressed(HipGamepadButton btn, bool pressed){buttons[btn].setPressed(pressed);} 104 105 void setAnalog(HipGamepadAnalogs analog, float[3] value) 106 { 107 value[0] = applyZones(value[0], deadZone, aliveZone); 108 value[1] = applyZones(value[1], deadZone, aliveZone); 109 value[2] = applyZones(value[2], deadZone, aliveZone); 110 111 final switch(analog) 112 { 113 case HipGamepadAnalogs.leftStick: 114 leftAnalog = value; 115 break; 116 case HipGamepadAnalogs.rightStick: 117 rightAnalog = value; 118 break; 119 case HipGamepadAnalogs.rightTrigger: 120 rightTrigger = value[0]; 121 break; 122 case HipGamepadAnalogs.leftTrigger: 123 leftTrigger = value[0]; 124 break; 125 } 126 } 127 bool isButtonPressed(HipGamepadButton btn){return buttons[btn].isPressed;} 128 bool isButtonJustPressed(HipGamepadButton btn){return buttons[btn].isJustPressed;} 129 bool isButtonJustReleased(HipGamepadButton btn){return buttons[btn].isJustReleased;} 130 131 bool areButtonsPressed(scope HipGamepadButton[] btns) 132 { 133 foreach(btn; btns) 134 if(!buttons[btn].isPressed) return false; 135 return true; 136 } 137 bool areButtonsJustPressed(scope HipGamepadButton[] btns) 138 { 139 foreach(btn; btns) 140 if(!buttons[btn].isJustPressed) return false; 141 return true; 142 } 143 bool areButtonsJustReleased(scope HipGamepadButton[] btns) 144 { 145 foreach(btn; btns) 146 if(!buttons[btn].isJustReleased) return false; 147 return true; 148 } 149 150 void poll(float deltaTime) 151 { 152 if(vibrationTime != 0) 153 { 154 vibrationAccumulator+= deltaTime; 155 if(vibrationAccumulator >= vibrationTime) 156 setVibrating(0,0); 157 } 158 159 if(_isConnected) 160 impl.poll(this); 161 } 162 163 void postUpdate() 164 { 165 for(int i =0; i < buttons.length; i++) 166 buttons[i]._isNewState = false; 167 } 168 169 final bool setVibrating(float time, double vibrationPower) 170 { 171 return setVibrating(time, vibrationPower,vibrationPower,vibrationPower,vibrationPower); 172 } 173 174 bool setVibrating(float time, double leftMotor, 175 double rightMotor, 176 double leftTrigger, 177 double rightTrigger 178 ) 179 { 180 impl.setVibrating(getId, 181 leftMotor, 182 rightMotor, 183 leftTrigger, 184 rightTrigger 185 ); 186 vibrationAccumulator = 0; 187 vibrationTime = time; 188 return true; 189 } 190 191 bool isWireless(){return impl.isWireless(getId);} 192 Vector3 getAnalogState(HipGamepadAnalogs analog) 193 { 194 final switch(analog) 195 { 196 case HipGamepadAnalogs.leftStick: return leftAnalog; 197 case HipGamepadAnalogs.rightStick: return rightAnalog; 198 case HipGamepadAnalogs.rightTrigger: return Vector3(rightTrigger, 0, 0); 199 case HipGamepadAnalogs.leftTrigger: return Vector3(leftTrigger, 0, 0); 200 } 201 } 202 float getBatteryStatus() 203 { 204 status = impl.getBatteryStatus(getId()); 205 return cast(float)status.remainingCapacityInMilliwattHours 206 / status.fullChargeCapacityInMilliwattHours; 207 } 208 } 209 210 void initGamepads() 211 { 212 import hip.systems.gamepads.xbox; 213 initXboxGamepadInput(); 214 }